#include "BlockManager.h"
#include <iostream>
#include <sstream>
#include "File.h"

namespace streamdb {

class StreamInfo {
public:
    StreamInfo(const std::string name, const std::string absPath)
    : name(name)
    , file(absPath)
    , maxBlockNo(-1)
    {
        file.openRW();
    }
    ~StreamInfo(){
        file.close();
    }

    const std::string & getName() const { return name; }

    File & getFile() { return file; }
    uint64_t getMaxBlockNo() const { return maxBlockNo; }
    uint64_t getNextMaxBlockNo() { return ++maxBlockNo; }


    Status writeBlock(uint64_t blockIndex, Block * block){
        if(block->isDirty){
            uint64_t cap = block->used;
            return file.writeByPosition(blockIndex * block->maxBlockSize , block->data, cap);
        }
        return Status::OK();
    }

private:
    std::string name;
    File file;
    uint64_t maxBlockNo; //TODO: atomic
};




BlockManager::BlockManager()
: maxBlockSize(64)
, cache(new Cache())
{
}
BlockManager::~BlockManager()
{
    delete cache;
}
std::string BlockManager::getAbsPath(const std::string & streamName){
    return streamName;
}

Status BlockManager::regStream(const std::string & streamName){
    std::cout <<" block manager reg:" << streamName << std::endl;

    auto iter = streamMap.find(streamName);
    if(iter == streamMap.end()){
        std::shared_ptr<StreamInfo> meta = std::make_shared<StreamInfo>(streamName, getAbsPath(streamName));
        streamMap.insert(std::make_pair(streamName, meta));
    }
    return Status::OK();
}
Status BlockManager::unregStream(const std::string & streamName){
    auto iter = streamMap.find(streamName);
    if(iter != streamMap.end()){
        streamMap.erase(iter);
    }

    std::cout <<" block manager unreg:" << streamName << std::endl;
    return Status::OK();
}
Block * BlockManager::newBlock(const std::string & streamName) {
    auto iter = streamMap.find(streamName);
    if(iter != streamMap.end()){ //stream find 
        std::string blockId = getBlockId(streamName, iter->second->getNextMaxBlockNo());
        std::cout << "newBlock : "<< blockId << std::endl;
        Block * block = new Block(blockId, maxBlockSize, iter->second);
        cache->insert(blockId, block);
        return block;
    }
   
    return nullptr;
}

std::string BlockManager::getBlockId(const std::string & streamName, uint64_t blockNo) const{
    //TODO: encode the blockIndex to string for performance
    std::ostringstream oss;
    oss << streamName << blockNo;
    return oss.str();
}

std::shared_ptr<StreamInfo> BlockManager::getStream(const std::string & streamName) {
    auto iter = streamMap.find(streamName);
    if(iter != streamMap.end()){
        return iter->second;
    }
    return nullptr;
}


Block *  BlockManager::getLastBlockFromStream(const std::string streamName) {
    auto stream = getStream(streamName);
    if(stream == nullptr) {
        return nullptr;
    }

    Block * block  = nullptr;
    auto maxBlockId =  stream->getMaxBlockNo();
    if(maxBlockId == -1) {
        block = newBlock(streamName);
    }
    else {
        std::string blockId = getBlockId(streamName, maxBlockId);
        block = cache->getBlock(blockId);
        if(block == nullptr) {
            //TODO: load to Cache
            //cache->insert
        }
    }
    assert(block != nullptr);
    return block;
}


Status BlockManager::append(const std::string & streamName, const rocksdb::Slice & msg){
    Block * block = getLastBlockFromStream(streamName);
    assert(block != nullptr);
    std::cout << "append to maxBlockId :" << block->key << "freeBytes:" << block->getFreeBytes() << std::endl;

    //1. Write First Block 
    const char * pos = msg.data();
    size_t needWriteBytes = msg.size();

    size_t writedBytes = 0;
    if(block != nullptr){
        writedBytes = block->append(pos, needWriteBytes);
        std::cout << "write first block bytes:: " << writedBytes << std::endl;
        if(writedBytes == needWriteBytes){
            return Status::OK();
        }
        if(writedBytes > needWriteBytes){
            return Status::Failed("block free Bytes error!");
        }
    }
    assert(needWriteBytes > writedBytes);
    needWriteBytes -= writedBytes;
    pos += writedBytes;

    //2. Need More Block to write -> extend to next more blocks
    size_t nblock = ((needWriteBytes%maxBlockSize) == 0) ? (needWriteBytes/maxBlockSize) : (needWriteBytes/maxBlockSize) + 1;
    for(int i = 0; i < nblock; ++i){
        auto * block = newBlock(streamName);
        assert(block != nullptr);
        writedBytes = block->append(pos, needWriteBytes);
        std::cout << "write next block bytes:: " << writedBytes << std::endl;
        assert(writedBytes >= 0);
        needWriteBytes -= writedBytes;
    }

    return Status::OK();
}
size_t BlockManager::getMaxBlockSize() const {
    return maxBlockSize;
}

Status BlockManager::flushStream(const std::string & streamName){
    auto iter = streamMap.find(streamName); 
    if(iter != streamMap.end()){
        auto streamInfo = iter->second;
        for( int i = 0; i <= streamInfo->getMaxBlockNo(); ++i){
            std::string blockId = getBlockId(streamName, i);
            auto block = cache->getBlock(blockId);
            if(block != nullptr){
                streamInfo->writeBlock(i, block);
            }
            else{
                std::cout << "block is nullptr:" << blockId  << std::endl;
            }
        }
    }
    return Status::OK();
}
Status BlockManager::flush(){ //checkpoint
    for(auto & pair : streamMap){
        flushStream(pair.first);
    }
    return Status::OK();
}

size_t BlockManager::getStreamBlockCount(const std::string & streamName){
    auto iter = streamMap.find(streamName); 
    if(iter != streamMap.end()){
        return (size_t) (iter->second->getMaxBlockNo() + 1);
    }
    return 0;
}

}//namespace streamdb
